/*
 * Copyright (c) 2010-2012, Freescale Semiconductor, Inc. All rights reserved.
 *
 */

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/*
 * Module Name:    TimeStamp.c
 *
 * Description:    include TimeStamp stratege for VPU / SW video decoder plugin
 *
 * Portability:    This code is written for Linux OS and Gstreamer
 */

/*
 * Changelog:
  11/2/2010        draft version       Lyon Wang
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "mfw_gst_ts.h"


const char *debug_env = "ME_DEBUG";
char *debug = NULL;
int debug_level = 0;


enum
{
  DEBUG_LEVEL_ERROR = 1,
  DEBUG_LEVEL_WARNING,
  DEBUG_LEVEL_LOG,
  DEBUG_LEVEL_VERBOSE,
};


#define TSM_MESSAGE(level, fmt, ...)\
  do{\
    if (debug_level>=(level)){\
      printf("TSM:"fmt, ##__VA_ARGS__);\
    }\
  }while(0)

#define TSM_ERROR(...) TSM_MESSAGE(DEBUG_LEVEL_ERROR, ##__VA_ARGS__)
#define TSM_WARNING(...) TSM_MESSAGE(DEBUG_LEVEL_WARNING, ##__VA_ARGS__)
#define TSM_LOG(...) TSM_MESSAGE(DEBUG_LEVEL_LOG, ##__VA_ARGS__)
#define TSM_VERBOSE(...) TSM_MESSAGE(DEBUG_LEVEL_VERBOSE, ##__VA_ARGS__)

#define TSM_HISTORY_POWER 5
#define TSM_HISTORY_SIZE (1<<TSM_HISTORY_POWER)
#define TSM_ADAPTIVE_INTERVAL(tsm) \
    (tsm->dur_history_total>>TSM_HISTORY_POWER)

#define TSM_SECOND ((TSM_TIMESTAMP)1000000000)
#define TSM_DEFAULT_INTERVAL (TSM_SECOND/30)
#define TSM_DEFAULT_TS_BUFFER_SIZE (128)

#define TSM_TS_IS_VALID(ts)	\
    ((ts) != TSM_TIMESTAMP_NONE)

#define TSM_KEY_IS_VALID(key) \
    ((key) != TSM_KEY_NONE)

#define TSM_DISTANCE(tsm)\
    (((tsm->rx)>=(tsm->tx))?((tsm->rx)-(tsm->tx)):(tsm->ts_buf_size-(tsm->tx)+(tsm->rx)))

#define TSM_PLUS_AGE(tsm)\
    (TSM_DISTANCE(tsm)+tsm->invalid_ts_count+2)

#define TSM_ABS(ts0, ts1)\
    (((ts0)>(ts1))?((ts0)-(ts1)):((ts1)-(ts0)))

#define TSM_TIME_FORMAT "u:%02u:%02u.%09u"

#define TSM_TIME_ARGS(t) \
        TSM_TS_IS_VALID (t) ? \
        (unsigned int) (((TSM_TIMESTAMP)(t)) / (TSM_SECOND * 60 * 60)) : 99, \
        TSM_TS_IS_VALID (t) ? \
        (unsigned int) ((((TSM_TIMESTAMP)(t)) / (TSM_SECOND * 60)) % 60) : 99, \
        TSM_TS_IS_VALID (t) ? \
        (unsigned int) ((((TSM_TIMESTAMP)(t)) / TSM_SECOND) % 60) : 99, \
        TSM_TS_IS_VALID (t) ? \
        (unsigned int) (((TSM_TIMESTAMP)(t)) % TSM_SECOND) : 999999999

#define TSM_BUFFER_SET(buf, value, size) \
    do {\
        int i;\
        for (i=0;i<(size);i++){\
            (buf)[i] = (value);\
        }\
    }while(0)

#define TSM_RECEIVED_NUNBER 512


typedef struct
{
  TSM_TIMESTAMP ts;
  unsigned long long age;
  void *key;
} TSMControl;

typedef struct _TSMReceivedEntry
{
  TSM_TIMESTAMP ts;
  struct _TSMReceivedEntry *next;
  unsigned int used:1;
  unsigned int subentry:1;
  int size;
} TSMReceivedEntry;

typedef struct _TSMReceivedEntryMemory
{
  struct _TSMReceivedEntryMemory *next;
  TSMReceivedEntry entrys[TSM_RECEIVED_NUNBER];
} TSMReceivedEntryMemory;

typedef struct
{
  TSMReceivedEntry *head;
  TSMReceivedEntry *tail;
  TSMReceivedEntry *free;
  TSMReceivedEntryMemory *memory;
  int cnt;
} TSMRecivedCtl;

typedef struct _TSManager
{
  int first_tx;
  int first_rx;
  int rx;                       //timestamps received
  int tx;                       //timestamps transfered
  TSM_TIMESTAMP last_ts_sent;   //last time stamp sent
  TSM_TIMESTAMP last_ts_received;
  TSM_TIMESTAMP suspicious_ts;

  TSM_TIMESTAMP discont_threshold;

  unsigned int invalid_ts_count;
  TSMGR_MODE mode;
  int ts_buf_size;
  int dur_history_tx;
  TSM_TIMESTAMP dur_history_total;
  TSM_TIMESTAMP dur_history_buf[TSM_HISTORY_SIZE];
  TSMControl *ts_buf;
  unsigned long long age;
  int tx_cnt;
  int rx_cnt;
  int cnt;
  int valid_ts_received:1;
  int big_cnt;

  TSMRecivedCtl rctl;
} TSManager;


static void
tsm_free_received_entry (TSMRecivedCtl * rctl, TSMReceivedEntry * entry)
{
  entry->next = rctl->free;
  rctl->free = entry;
}


static TSMReceivedEntry *
tsm_new_received_entry (TSMRecivedCtl * rctl)
{
  TSMReceivedEntry *ret = NULL;
  if (rctl->free) {
    ret = rctl->free;
    rctl->free = ret->next;
  } else {
    TSMReceivedEntryMemory *p = malloc (sizeof (TSMReceivedEntryMemory));
    if (p) {
      int i;
      for (i = 1; i < TSM_RECEIVED_NUNBER; i++) {
        TSMReceivedEntry *e = &p->entrys[i];
        tsm_free_received_entry (rctl, e);
      };

      p->next = rctl->memory;
      rctl->memory = p;

      ret = p->entrys;
    }
  }
  return ret;
}


void
TSManagerReceive2 (void *handle, TSM_TIMESTAMP timestamp, int size)
{
#define CLEAR_TSM_RENTRY(entry)\
  do { \
    (entry)->used = 0; \
    (entry)->subentry = 0; \
    (entry)->next = NULL; \
  } while (0)
  TSManager *tsm = (TSManager *) handle;

  TSM_VERBOSE ("receive2 %" TSM_TIME_FORMAT " size %d\n",
      TSM_TIME_ARGS (timestamp), size);

  if (tsm) {
    if (size > 0) {
      TSMRecivedCtl *rctl = &tsm->rctl;
      TSMReceivedEntry *e = tsm_new_received_entry (rctl);
      if (e) {
        CLEAR_TSM_RENTRY (e);
        if ((rctl->tail) && (rctl->tail->ts == timestamp)) {
          e->subentry = 1;
        }
        e->ts = timestamp;
        e->size = size;
        if (rctl->tail) {
          rctl->tail->next = e;
          rctl->tail = e;
        } else {
          rctl->head = rctl->tail = e;
        }
      }
      rctl->cnt++;
    } else {
      TSManagerReceive (handle, timestamp);
    }
  }
}


static TSM_TIMESTAMP
TSManagerGetLastTimeStamp (TSMRecivedCtl * rctl, int size, int use)
{
  TSM_TIMESTAMP ts = TSM_TIMESTAMP_NONE;
  TSMReceivedEntry *e;
  while ((size > 0) && (e = rctl->head)) {
    ts = ((e->used) ? (TSM_TIMESTAMP_NONE) : (e->ts));

    TSM_VERBOSE ("ts get: %" TSM_TIME_FORMAT "\n",
        TSM_TIME_ARGS (ts));

    if (use)
      e->used = 1;
    if (size >= e->size) {
      rctl->head = e->next;
      if (rctl->head == NULL) {
        rctl->tail = NULL;
      } else {
#if 0
        //removed for rtp/rtsp streaming fix,
        //this will make same timestamp buffers output timestamp to -1.
        if (rctl->head->subentry) {
          rctl->head->used = e->used;
        }
#endif
      }
      size -= e->size;
      rctl->cnt--;
      tsm_free_received_entry (rctl, e);
    } else {
      e->size -= size;
      size = 0;
    }
  }
  return ts;
}


void
TSManagerFlush2 (void *handle, int size)
{
  TSManager *tsm = (TSManager *) handle;
  if (tsm) {
    TSManagerGetLastTimeStamp (&tsm->rctl, size, 0);
  }

}


/*======================================================================================
FUNCTION:           mfw_gst_receive_ts

DESCRIPTION:        Check timestamp and do frame dropping if enabled

ARGUMENTS PASSED:   pTimeStamp_Object  - TimeStamp Manager to handle related timestamp
                    timestamp - time stamp of the input buffer which has video data.

RETURN VALUE:       None
PRE-CONDITIONS:     None
POST-CONDITIONS:    None
IMPORTANT NOTES:    None
=======================================================================================*/
static void
_TSManagerReceive (void *handle, TSM_TIMESTAMP timestamp, void *key)
{
  TSManager *tsm = (TSManager *) handle;

  if (tsm) {
    if (TSM_TS_IS_VALID (timestamp) && (tsm->rx_cnt))
      tsm->valid_ts_received = 1;
    tsm->rx_cnt++;
    if (tsm->cnt < tsm->ts_buf_size - 1) {
      tsm->cnt++;
      if (tsm->mode == MODE_AI) {

        if (TSM_TS_IS_VALID (timestamp)) {
          if (tsm->first_rx) {
            tsm->last_ts_received = timestamp;
            tsm->first_rx = 0;
          } else {
            if (tsm->suspicious_ts) {
              if (timestamp >= tsm->suspicious_ts) {
                tsm->last_ts_received = timestamp;
              }
              tsm->suspicious_ts = 0;
            }
            if ((timestamp > tsm->last_ts_received)
                && (timestamp - tsm->last_ts_received > tsm->discont_threshold)) {
              tsm->suspicious_ts = timestamp;
              timestamp = TSM_TIMESTAMP_NONE;
            }
          }
        }

        if (TSM_TS_IS_VALID (timestamp))        // && (TSM_ABS(timestamp, tsm->last_ts_sent)<TSM_SECOND*10))
        {
          tsm->ts_buf[tsm->rx].ts = timestamp;
          tsm->ts_buf[tsm->rx].age = tsm->age + TSM_PLUS_AGE (tsm);
          tsm->ts_buf[tsm->rx].key = key;
          tsm->last_ts_received = timestamp;
#ifdef DEBUG
          //printf("age should %lld %lld\n", tsm->age, tsm->ts_buf[tsm->rx].age);
          //printf("++++++ distance = %d  tx=%d, rx=%d, invalid count=%d\n", TSM_DISTANCE(tsm), tsm->tx, tsm->rx,tsm->invalid_ts_count);
#endif
          tsm->rx = ((tsm->rx + 1) % tsm->ts_buf_size);
        } else {
          tsm->invalid_ts_count++;
        }
      } else if (tsm->mode == MODE_FIFO) {
        tsm->ts_buf[tsm->rx].ts = timestamp;
        tsm->rx = ((tsm->rx + 1) % tsm->ts_buf_size);
      }
      TSM_LOG ("++Receive %d:%" TSM_TIME_FORMAT
          ", invalid:%d, size:%d key %p\n", tsm->rx_cnt,
          TSM_TIME_ARGS (timestamp), tsm->invalid_ts_count, tsm->cnt, key);
    } else {
      TSM_ERROR ("Too many timestamps recieved!! (cnt=%d)\n", tsm->cnt);
    }
  }
}


void
TSManagerValid2 (void *handle, int size, void *key)
{
  TSManager *tsm = (TSManager *) handle;

  TSM_VERBOSE ("valid2 size %d\n", size);

  if (tsm) {
    TSM_TIMESTAMP ts;
    ts = TSManagerGetLastTimeStamp (&tsm->rctl, size, 1);
    TSM_VERBOSE ("TSManagerGetLastTimeStamp: %" TSM_TIME_FORMAT "\n",
        TSM_TIME_ARGS (ts));
    _TSManagerReceive (tsm, ts, key);
  }
}


void
TSManagerReceive (void *handle, TSM_TIMESTAMP timestamp)
{
  _TSManagerReceive (handle, timestamp, TSM_KEY_NONE);
}


/*======================================================================================
FUNCTION:           TSManagerSend

DESCRIPTION:        Check timestamp and do frame dropping if enabled

ARGUMENTS PASSED:   pTimeStamp_Object  - TimeStamp Manager to handle related timestamp
                    ptimestamp - returned timestamp to use at render

RETURN VALUE:       None
PRE-CONDITIONS:     None
POST-CONDITIONS:    None
IMPORTANT NOTES:    None
=======================================================================================*/
static TSM_TIMESTAMP
_TSManagerSend2 (void *handle, void *key, int send)
{
  TSManager *tsm = (TSManager *) handle;
  int i;
  int index = -1;
  TSM_TIMESTAMP ts0 = 0, tstmp = TSM_TIMESTAMP_NONE;
  unsigned long long age = 0;
  TSM_TIMESTAMP half_interval;

  if (tsm) {
    i = tsm->tx;
    half_interval = TSM_ADAPTIVE_INTERVAL (tsm) >> 1;
    if (send) {
      tsm->tx_cnt++;
    } else {
      tsm->cnt++;
      tsm->invalid_ts_count++;
    }
    if (tsm->cnt > 0) {
      if (send) {
        tsm->cnt--;
      }
      if (tsm->mode == MODE_AI) {

        if (tsm->first_tx == 0) {
          tstmp = tsm->last_ts_sent + TSM_ADAPTIVE_INTERVAL (tsm);
        } else {
          tstmp = tsm->last_ts_sent;
        }

        while (i != tsm->rx) {
          if (index >= 0) {
            if (tsm->ts_buf[i].ts < ts0) {
              ts0 = tsm->ts_buf[i].ts;
              age = tsm->ts_buf[i].age;
              index = i;
            }
          } else {
            ts0 = tsm->ts_buf[i].ts;
            age = tsm->ts_buf[i].age;
            index = i;
          }
          if ((TSM_KEY_IS_VALID (key)) && (key == tsm->ts_buf[i].key))
            break;
          i = ((i + 1) % tsm->ts_buf_size);
        }
        if (index >= 0) {
          if ((tsm->invalid_ts_count) && (ts0 >= ((tstmp) + half_interval))
              && (age > tsm->age)) {
            /* use calculated ts0 */
            if (send) {
              tsm->invalid_ts_count--;
            }
          } else {

            if (send) {
              if (index != tsm->tx) {
                tsm->ts_buf[index] = tsm->ts_buf[tsm->tx];
              }
              tsm->tx = ((tsm->tx + 1) % tsm->ts_buf_size);

            }
#if 0
            if (ts0 >= ((tstmp) + half_interval))
              tstmp = tstmp;
            else
              tstmp = ts0;
#else
            tstmp = ts0;
#endif
          }

        } else {
          if (send) {
            tsm->invalid_ts_count--;
          }
        }

        if (tsm->first_tx == 0) {

          if (tstmp > tsm->last_ts_sent) {
            ts0 = (tstmp - tsm->last_ts_sent);
          } else {
            ts0 = 0;
            tstmp = tsm->last_ts_sent;
          }

          if (ts0 > TSM_ADAPTIVE_INTERVAL (tsm) * 3 / 2) {
            TSM_WARNING ("Jitter1:%" TSM_TIME_FORMAT " %" TSM_TIME_FORMAT "\n",
                TSM_TIME_ARGS (ts0),
                TSM_TIME_ARGS (TSM_ADAPTIVE_INTERVAL (tsm) * 3 / 2));
          } else if (ts0 == 0) {
            TSM_WARNING ("Jitter:%" TSM_TIME_FORMAT "\n", TSM_TIME_ARGS (ts0));
          }

          if (send) {
            if ((ts0 < TSM_ADAPTIVE_INTERVAL (tsm) * 5) || (tsm->big_cnt > 3)) {
              tsm->big_cnt = 0;
              tsm->dur_history_total -=
                  tsm->dur_history_buf[tsm->dur_history_tx];
              tsm->dur_history_buf[tsm->dur_history_tx] = ts0;
              tsm->dur_history_tx =
                  ((tsm->dur_history_tx + 1) % TSM_HISTORY_SIZE);
              tsm->dur_history_total += ts0;
            } else {
              tsm->big_cnt++;
            }
          }
        }

        if (send) {
          tsm->last_ts_sent = tstmp;
          tsm->age++;
          tsm->first_tx = 0;
        }

      } else if (tsm->mode == MODE_FIFO) {
        tstmp = tsm->ts_buf[tsm->tx].ts;
        if (send) {
          tsm->tx = ((tsm->tx + 1) % tsm->ts_buf_size);
        }
        ts0 = tstmp - tsm->last_ts_sent;
        if (send) {
          tsm->last_ts_sent = tstmp;
        }
      }

      if (send) {
        TSM_LOG ("--Send %d:%" TSM_TIME_FORMAT ", int:%" TSM_TIME_FORMAT
            ", avg:%" TSM_TIME_FORMAT " inkey %p\n", tsm->tx_cnt,
            TSM_TIME_ARGS (tstmp), TSM_TIME_ARGS (ts0),
            TSM_TIME_ARGS (TSM_ADAPTIVE_INTERVAL (tsm)), key);
      }

    } else {
      if (tsm->valid_ts_received == 0) {
        if (tsm->first_tx) {
          tstmp = tsm->last_ts_sent;
        } else {
          tstmp = tsm->last_ts_sent + TSM_ADAPTIVE_INTERVAL (tsm);
        }
        if (send) {
          tsm->first_tx = 0;
          tsm->last_ts_sent = tstmp;
        }
      }
      TSM_ERROR ("Too many timestamps send!!\n");
    }

    if (send == 0) {
      tsm->cnt--;
      tsm->invalid_ts_count--;
    }

  }

  return tstmp;
}


TSM_TIMESTAMP
TSManagerSend2 (void *handle, void *key)
{
  return _TSManagerSend2 (handle, key, 1);
}


TSM_TIMESTAMP
TSManagerQuery2 (void *handle, void *key)
{
  return _TSManagerSend2 (handle, key, 0);
}


TSM_TIMESTAMP
TSManagerSend (void *handle)
{
  return TSManagerSend2 (handle, TSM_KEY_NONE);
}


TSM_TIMESTAMP
TSManagerQuery (void *handle)
{
  return TSManagerQuery2 (handle, TSM_KEY_NONE);
}


void
resyncTSManager (void *handle, TSM_TIMESTAMP synctime, TSMGR_MODE mode)
{
  TSManager *tsm = (TSManager *) handle;
  if (tsm) {
    TSMRecivedCtl *rctl = &tsm->rctl;
    TSMReceivedEntry *e = rctl->head;

    while ((e = rctl->head)) {
      rctl->head = e->next;
      tsm_free_received_entry (rctl, e);
    };
    rctl->cnt = 0;

    rctl->tail = NULL;

    tsm->first_tx = 1;
    tsm->first_rx = 1;
    tsm->suspicious_ts = 0;

    if (TSM_TS_IS_VALID (synctime))
      tsm->last_ts_sent = synctime;

    tsm->tx = tsm->rx = 0;
    tsm->invalid_ts_count = 0;
    tsm->mode = mode;
    tsm->age = 0;
    tsm->rx_cnt = tsm->tx_cnt = tsm->cnt = 0;
    tsm->valid_ts_received = 0;

    tsm->big_cnt = 0;
  }
}


/*======================================================================================
FUNCTION:           mfw_gst_init_ts

DESCRIPTION:        malloc and initialize timestamp strcture

ARGUMENTS PASSED:   ppTimeStamp_Object  - pointer of TimeStamp Manager to handle related timestamp

RETURN VALUE:       TimeStamp structure pointer
PRE-CONDITIONS:     None
POST-CONDITIONS:    None
IMPORTANT NOTES:    None
=======================================================================================*/
void *
createTSManager (int ts_buf_size)
{
  TSManager *tsm = (TSManager *) malloc (sizeof (TSManager));
  debug = getenv (debug_env);
  if (debug) {
    debug_level = atoi (debug);
  }
  // printf("debug = %s \n ++++++++++++++++++++++++++++",debug);
  if (tsm) {
    memset (tsm, 0, sizeof (TSManager));
    if (ts_buf_size <= 0) {
      ts_buf_size = TSM_DEFAULT_TS_BUFFER_SIZE;
    }
    tsm->ts_buf_size = ts_buf_size;
    tsm->ts_buf = malloc (sizeof (TSMControl) * ts_buf_size);

    if (tsm->ts_buf == NULL) {
      goto fail;
    }

    resyncTSManager (tsm, (TSM_TIMESTAMP) 0, MODE_AI);

    tsm->dur_history_tx = 0;
    TSM_BUFFER_SET (tsm->dur_history_buf, TSM_DEFAULT_INTERVAL,
        TSM_HISTORY_SIZE);
    tsm->dur_history_total = TSM_DEFAULT_INTERVAL << TSM_HISTORY_POWER;

    tsm->discont_threshold = 10000000000LL;     // 10s
  }
  return tsm;
fail:
  if (tsm) {
    if (tsm->ts_buf) {
      free (tsm->ts_buf);
    }
    free (tsm);
    tsm = NULL;
  }
  return tsm;
}


void
destroyTSManager (void *handle)
{
  TSManager *tsm = (TSManager *) handle;
  if (tsm) {
    TSMRecivedCtl *rctl = &tsm->rctl;
    TSMReceivedEntryMemory *rmem;
    if (tsm->ts_buf) {
      free (tsm->ts_buf);
    }

    while ((rmem = rctl->memory)) {
      rctl->memory = rmem->next;
      free (rmem);
    }
    free (tsm);
    tsm = NULL;
  }
}


void
setTSManagerFrameRate (void *handle, int fps_n, int fps_d)
//void setTSManagerFrameRate(void * handle, float framerate)
{
  TSManager *tsm = (TSManager *) handle;
  TSM_TIMESTAMP ts;
  if ((fps_n > 0) && (fps_d > 0) && (fps_n / fps_d <= 80))
    ts = TSM_SECOND * fps_d / fps_n;
  else
    ts = TSM_DEFAULT_INTERVAL;
  // TSM_TIMESTAMP ts = TSM_SECOND / framerate;

  if (tsm) {
    TSM_BUFFER_SET (tsm->dur_history_buf, ts, TSM_HISTORY_SIZE);
    tsm->dur_history_total = (ts << TSM_HISTORY_POWER);
    if (debug)
      TSM_LOG ("Set frame intrval:%" TSM_TIME_FORMAT "\n", TSM_TIME_ARGS (ts));
  }
}


TSM_TIMESTAMP
getTSManagerFrameInterval (void *handle)
{
  TSManager *tsm = (TSManager *) handle;
  TSM_TIMESTAMP ts = 0;
  if (tsm) {
    ts = TSM_ADAPTIVE_INTERVAL (tsm);
  }
  return ts;
}


TSM_TIMESTAMP
getTSManagerPosition (void *handle)
{
  TSManager *tsm = (TSManager *) handle;
  TSM_TIMESTAMP ts = 0;
  if (tsm) {
    ts = tsm->last_ts_sent;
  }
  return ts;
}


int
getTSManagerPreBufferCnt (void *handle)
{
  int i = 0;
  TSManager *tsm = (TSManager *) handle;
  if (tsm) {
    i = tsm->rctl.cnt;
  }
  return i;
}
